// license is unknown

// original source:
// https://www.youtube.com/watch?v=CYkGRtVv3Ls

#include "xparameters.h"	/* EDK generated parameters */
#include "xspi.h"		/* SPI device driver */
#include "xil_printf.h"

#include "qspi/qspi.h"

#include "gpio/gpio.h"

#include "Timer.h"

#include "stdio.h"
#include "ssd1331/ssd1331.h"

#define SPI_DEVICE_ID			XPAR_SPI_0_DEVICE_ID
#define SPI_SELECT 				0x01

static XSpi Spi;
//#define SPI1	0

extern Timer timer;

extern "C" void atca_delay_us(uint32_t delay);

namespace SSD1331 {

	#define SSD1331_DC		(1<<9)
	#define SSD1331_nRES	(1<<10)
	#define SSD1331_VCCEN	(1<<11)
	#define SSD1331_PMODEN	(1<<12)

	namespace {
		const uint8_t initSequence[] = {
			CMD_DISPLAY_OFF,          //Display Off
			CMD_SET_CONTRAST_A,       //Set contrast for color A
			0x91,                     //145 (0x91)
			CMD_SET_CONTRAST_B,       //Set contrast for color B
			0x50,                     //80 (0x50)
			CMD_SET_CONTRAST_C,       //Set contrast for color C
			0x7D,                     //125 (0x7D)
			CMD_MASTER_CURRENT_CONTROL,               //master current control
			0x06,                     //6
			CMD_SET_PRECHARGE_SPEED_A, //Set Second Pre-change Speed For ColorA
			0x64,                     //100
			CMD_SET_PRECHARGE_SPEED_B, //Set Second Pre-change Speed For ColorB
			0x78,                     //120
			CMD_SET_PRECHARGE_SPEED_C, //Set Second Pre-change Speed For ColorC
			0x64,                     //100
			CMD_SET_REMAP,            //set remap & data format
			0x60, // rotate 180°
			CMD_SET_DISPLAY_START_LINE,               //Set display Start Line
			0x0,
			CMD_SET_DISPLAY_OFFSET,   //Set display offset
			0x0,
			CMD_NORMAL_DISPLAY,       //Set display mode
			CMD_SET_MULTIPLEX_RATIO,  //Set multiplex ratio
			0x3F,
			CMD_SET_MASTER_CONFIGURE, //Set master configuration
			0x8E,
			CMD_POWER_SAVE_MODE,      //Set Power Save Mode
			0x00,                     //0x00
			CMD_PHASE_PERIOD_ADJUSTMENT,     //phase 1 and 2 period adjustment
			0x31,                     //0x31
			CMD_DISPLAY_CLOCK_DIV, //display clock divider/oscillator frequency
			0xF0,
			CMD_SET_PRECHARGE_VOLTAGE,    //Set Pre-Change Level
			0x3A,
			CMD_SET_V_VOLTAGE,        //Set vcomH
			0x3E,
			CMD_DEACTIVE_SCROLLING,   //disable scrolling
			CMD_NORMAL_BRIGHTNESS_DISPLAY_ON,   //set display on
		};

		unsigned char CHR_X, CHR_Y;

		int init_spi() {
			int Status;
			XSpi_Config *ConfigPtr; /* Pointer to Configuration data */

			ConfigPtr = XSpi_LookupConfig(SPI_DEVICE_ID);
			if (ConfigPtr == NULL) {
				return XST_DEVICE_NOT_FOUND;
			}

			Status = XSpi_CfgInitialize(&Spi, ConfigPtr, ConfigPtr->BaseAddress);
			if (Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			Status = XSpi_SetOptions(&Spi, XSP_MASTER_OPTION |
			XSP_MANUAL_SSELECT_OPTION);
			if (Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			Status = XSpi_SetSlaveSelect(&Spi, SPI_SELECT);
			if (Status != XST_SUCCESS) {
				return XST_FAILURE;
			}

			XSpi_Start(&Spi);
			XSpi_IntrGlobalDisable(&Spi);	// use polling-mode

			return XST_SUCCESS;
		}

		inline void clrDC() {
			GPIO::clrBit(SSD1331_DC);
		}

		inline void setDC() {
			GPIO::setBit(SSD1331_DC);
		}

		inline void clrSS() {
			GPIO::clrBit(GPIO_SS0);
		}

		inline void setSS() {
			GPIO::setBit(GPIO_SS0);
		}

		int sendSPI(uint8_t c) {
			int Status = XSpi_Transfer(&Spi, &c, NULL, 1);
			if (Status != XST_SUCCESS) {
				return XST_FAILURE;
			}
			return XST_SUCCESS;
		}

		void sendData(uint16_t c) {
			setDC();
			clrSS();
			sendSPI((c & 0xff00) >> 8);
			sendSPI(c & 0x00ff);
			setSS();
		}


		void sendCmd(uint8_t c) {
			clrDC();
			clrSS();
			sendSPI(c);
			setSS();
		}
	}


	void init(void) {
		GPIO::setAF(GPIO_MOSI0 | GPIO_MISO1 | GPIO_SCK0 /*| GPIO_SS0*/, 1);

		GPIO::setDir(
		SSD1331_DC | SSD1331_nRES | SSD1331_VCCEN | SSD1331_PMODEN | GPIO_SS0, 1);
		GPIO::setBit(SSD1331_nRES | SSD1331_VCCEN | SSD1331_PMODEN | GPIO_SS0);
		GPIO::clrBit(SSD1331_DC);

		init_spi();

		for (size_t i=0;i<sizeof(initSequence);i++) {
			sendCmd(initSequence[i]);
			timer.sleep(1);	// needs some time after commands
		}

		timer.sleep(100);
		drawFrame(0, 0, 96, 64, COLOR_BLACK, COLOR_BLACK);
	}

	static inline void setColRow(uint16_t x, uint16_t y) {
		//set column point
		sendCmd(CMD_SET_COLUMN_ADDRESS);
		sendCmd(x);
		sendCmd(RGB_OLED_WIDTH - 1);
		//set row point
		sendCmd(CMD_SET_ROW_ADDRESS);
		sendCmd(y);
		sendCmd(RGB_OLED_HEIGHT - 1);
	}

	void drawPixel(uint16_t x, uint16_t y, uint16_t color) {
		if ((x >= RGB_OLED_WIDTH) || (y >= RGB_OLED_HEIGHT))
			return;
		setColRow(x, y);
		// send 16bit color
		sendData(color);
	}

	void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1,
			uint16_t color) {

		if (x0 >= RGB_OLED_WIDTH)
			x0 = RGB_OLED_WIDTH - 1;
		if (y0 >= RGB_OLED_HEIGHT)
			y0 = RGB_OLED_HEIGHT - 1;
		if (x1 >= RGB_OLED_WIDTH)
			x1 = RGB_OLED_WIDTH - 1;
		if (y1 >= RGB_OLED_HEIGHT)
			y1 = RGB_OLED_HEIGHT - 1;

		sendCmd(CMD_DRAW_LINE); //draw line
		sendCmd(x0); //start column
		sendCmd(y0); //start row
		sendCmd(x1); //end column
		sendCmd(y1); //end row
		sendCmd((uint8_t) ((color >> 11) & 0x1F)); //R
		sendCmd((uint8_t) ((color >> 5) & 0x3F)); //G
		sendCmd((uint8_t) (color & 0x1F)); //B
	}

	void drawFrame(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1,
			uint16_t outColor, uint16_t fillColor) {

		if (x0 >= RGB_OLED_WIDTH)
			x0 = RGB_OLED_WIDTH - 1;
		if (y0 >= RGB_OLED_HEIGHT)
			y0 = RGB_OLED_HEIGHT - 1;
		if (x1 >= RGB_OLED_WIDTH)
			x1 = RGB_OLED_WIDTH - 1;
		if (y1 >= RGB_OLED_HEIGHT)
			y1 = RGB_OLED_HEIGHT - 1;

		sendCmd(CMD_FILL_WINDOW); //fill window
		sendCmd(ENABLE_FILL);
		sendCmd(CMD_DRAW_RECTANGLE); //draw rectangle
		sendCmd(x0); //start column
		sendCmd(y0); //start row
		sendCmd(x1); //end column
		sendCmd(y1); //end row
		sendCmd((uint8_t) ((outColor >> 11) & 0x1F)); //R
		sendCmd((uint8_t) ((outColor >> 5) & 0x3F)); //G
		sendCmd((uint8_t) (outColor & 0x1F)); //B
		sendCmd((uint8_t) ((fillColor >> 11) & 0x1F)); //R
		sendCmd((uint8_t) ((fillColor >> 5) & 0x3F)); //G
		sendCmd((uint8_t) (fillColor & 0x1F)); //B
	}

	void drawCircle(uint16_t x, uint16_t y, uint16_t radius,
			uint16_t color) {
		signed char xc = 0;
		signed char yc = 0;
		signed char p = 0;

		// Out of range
		if (x >= RGB_OLED_WIDTH || y >= RGB_OLED_HEIGHT)
			return;

		yc = radius;
		p = 3 - (radius << 1);
		while (xc <= yc) {
			drawPixel(x + xc, y + yc, color);
			drawPixel(x + xc, y - yc, color);
			drawPixel(x - xc, y + yc, color);
			drawPixel(x - xc, y - yc, color);
			drawPixel(x + yc, y + xc, color);
			drawPixel(x + yc, y - xc, color);
			drawPixel(x - yc, y + xc, color);
			drawPixel(x - yc, y - xc, color);
			if (p < 0)
				p += (xc++ << 2) + 6;
			else
				p += ((xc++ - yc--) << 2) + 10;
		}

	}

	// Set current position in cache
	void setXY(unsigned char x, unsigned char y) {
		CHR_X = x;
		CHR_Y = y;
	}

	void incXY(Font font) {
		CHR_X += font.m_width + 1;
		if (CHR_X + font.m_width + 1 > RGB_OLED_WIDTH) {
			CHR_X = 0;
			CHR_Y += font.m_height + 1;
			if (CHR_Y + font.m_height + 1 > RGB_OLED_HEIGHT) {
				CHR_Y = 0;
			}
		}
	}

	void putChar(Font font, unsigned char ch, uint16_t chr_color,
			uint16_t bg_color) {

		if ((ch >= 0x20) && (ch <= 0x7F)) {
			ch -= 32;
		} else {
			ch = 95;
		}

		const uint8_t* p = &font.m_data[ch * font.m_width];
		for (int y=0;y<font.m_height;y++) {
			setColRow(CHR_X, CHR_Y + y);
			// send 16bit color
			for (int x=0;x<font.m_width;x++) {
				sendData((p[x] & (1<<y)) ? chr_color : bg_color);
			}
			// separator
			sendData(bg_color);
		}
	}

	// Print a string to display
	void printString(Font font, const char* s,
			uint16_t chr_color, uint16_t bg_color) {
		while (*s) {
			putChar(font, *s++, chr_color, bg_color);
			incXY(font);
		}
	}

	void printStringCentered(Font font, const char* s, uint16_t chr_color, uint16_t bg_color) {
		size_t len = strlen(s);
		int x = ((RGB_OLED_WIDTH  - (len * (font.m_width+1))) / 2);
		x = (x<0) ? 0 : x;
		setXY(x, CHR_Y);
		printString(font, s, chr_color, bg_color);
	}


#if 0
	void SSD1331_IMG(const unsigned char *img, uint16_t x, uint16_t y,
			uint16_t width, uint16_t height) {
		uint16_t xx, yy;

		if ((x + width > RGB_OLED_WIDTH) | (y + height > RGB_OLED_HEIGHT)) {
			return;
		}

		for (yy = 0; yy < height; yy++) {
			//set column point
			sendCmd(CMD_SET_COLUMN_ADDRESS);
			sendCmd(x);
			sendCmd(RGB_OLED_WIDTH - 1);
			//set row point
			sendCmd(CMD_SET_ROW_ADDRESS);
			sendCmd(y + yy);
			sendCmd(RGB_OLED_HEIGHT - 1);
			setDC();
			clrSS();

			for (xx = 0; xx < width * 2; xx++) {
				sendData(img[yy * width * 2 + xx]);
			}
		}
	}

	void copyWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1,
			uint16_t x2, uint16_t y2) {
		sendCmd(CMD_COPY_WINDOW); //copy window
		sendCmd(x0); //start column
		sendCmd(y0); //start row
		sendCmd(x1); //end column
		sendCmd(y1); //end row
		sendCmd(x2); //new column
		sendCmd(y2); //new row
	}

	void dimWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
		sendCmd(CMD_DIM_WINDOW); //copy area
		sendCmd(x0); //start column
		sendCmd(y0); //start row
		sendCmd(x1); //end column
		sendCmd(y1); //end row
	}

	void clearWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
		sendCmd(CMD_CLEAR_WINDOW); //clear window
		sendCmd(x0); //start column
		sendCmd(y0); //start row
		sendCmd(x1); //end column
		sendCmd(y1); //end row
	}

	void setScrolling(ScollingDirection direction, uint8_t rowAddr,
			uint8_t rowNum, uint8_t timeInterval) {
		uint8_t scolling_horizontal = 0x0;
		uint8_t scolling_vertical = 0x0;
		switch (direction) {
		case Horizontal:
			scolling_horizontal = 0x01;
			scolling_vertical = 0x00;
			break;
		case Vertical:
			scolling_horizontal = 0x00;
			scolling_vertical = 0x01;
			break;
		case Diagonal:
			scolling_horizontal = 0x01;
			scolling_vertical = 0x01;
			break;
		default:
			break;
		}
		sendCmd(CMD_CONTINUOUS_SCROLLING_SETUP);
		sendCmd(scolling_horizontal);
		sendCmd(rowAddr);
		sendCmd(rowNum);
		sendCmd(scolling_vertical);
		sendCmd(timeInterval);
		sendCmd(CMD_ACTIVE_SCROLLING);
	}

	void enableScrolling(bool enable) {
		if (enable)
			sendCmd(CMD_ACTIVE_SCROLLING);
		else
			sendCmd(CMD_DEACTIVE_SCROLLING);
	}
#endif
	void setDisplayMode(DisplayMode mode) {
		sendCmd(mode);
	}

	void SSD1331_setDisplayPower(DisplayPower power) {
		sendCmd(power);
	}
}
